package com.yiwyn.flowdemo.flow.engine;

import com.yiwyn.flowdemo.BusinessNode.BusinessHandlerRouter;
import com.yiwyn.flowdemo.domain.*;
import com.yiwyn.flowdemo.flow.base.FlowContext;
import com.yiwyn.flowdemo.flow.common.FlowStatusEnum;
import com.yiwyn.flowdemo.flow.common.NodeTypeEnum;
import com.yiwyn.flowdemo.flow.entity.BusinessHandler;
import com.yiwyn.flowdemo.service.impl.*;
import com.yiwyn.flowdemo.utils.AviatorUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 工作流引擎
 */
@Slf4j
@Component
public class FlowEngine {

    @Autowired
    private FlowNodeServiceImpl flowNodeService;
    @Autowired
    private NodeRouterServiceImpl nodeRouterService;
    @Autowired
    private ProcessDefinitionServiceImpl processDefinitionService;
    @Autowired
    private ProcessInstanceServiceImpl processInstanceService;
    @Autowired
    private NodeHistoryServiceImpl nodeHistoryService;
    @Autowired
    private PlatformTransactionManager transactionManager;

    /**
     * 初始化实例
     */
    public ProcessInstance createInstance(String flowDomain, Map<String, Object> variables, String businessKey) throws Exception {
        return createInstance(flowDomain, businessKey, variables, true);
    }


    public ProcessInstance createInstance(String flowDomain, String businessKey, Map<String, Object> variables, Boolean doFirstNode) throws Exception {
        log.info("创建流程:{} 流程业务主键:{}", flowDomain, businessKey);
        ProcessInstance processInstance = buildInstance(flowDomain, businessKey);
        /**
         * 历史轨迹
         */
        NodeHistory historyEntity = new NodeHistory();
        historyEntity.setNodeId(processInstance.getCurrentNodeId());
        historyEntity.setNodeName(processInstance.getCurrentNodeId());
        historyEntity.setStartTime(new Date());
        historyEntity.setInstanceId(processInstance.getInstanceId());
        historyEntity.setCompleted(false);
        nodeHistoryService.save(historyEntity);

        processInstance.setHistoryId(historyEntity.getId());
        processInstanceService.save(processInstance);
        if (doFirstNode) {
            claimTask(processInstance.getInstanceId(), NodeTypeEnum.AUTO_TASK.toString());
            complete(processInstance.getInstanceId(), variables);
        } else {
            claimTask(processInstance.getInstanceId(), businessKey);
        }
        return processInstance;
    }


    /**
     * 签收流程
     *
     * @param instanceId 流程id
     * @param assignee   签收用户
     */
    public void claimTask(String instanceId, String assignee) {
        // 找到流程实例
        ProcessInstance processInstance = processInstanceService.lambdaQuery().eq(ProcessInstance::getInstanceId, instanceId).one();
        /* 这里可以做校验等各种行为 */
        if (processInstance.getStatus().equals(FlowStatusEnum.COMPLETED.getStatus())) {
            throw new IllegalStateException("流程已结束");
        }

        // 签收流程
        processInstance.setAssignee(assignee);
        processInstance.setUpdateTime(new Date());
        log.info("InstanceId:{} 任务 {} 被 {}签收", instanceId, processInstance.getCurrentNodeId(), assignee);
        processInstanceService.saveOrUpdate(processInstance);

        Integer historyId = processInstance.getHistoryId();
        if (historyId != null) {
            NodeHistory currentHistory = nodeHistoryService.getById(historyId);
            currentHistory.setClaimTime(new Date());
            currentHistory.setAssignee(assignee);
            nodeHistoryService.updateById(currentHistory);
        }

        // 执行启动方法
        String currentFlowNodeId = processInstance.getCurrentNodeId();
        // 获取节点处理
        FlowNode flowNode = flowNodeService.lambdaQuery().eq(FlowNode::getNodeId, currentFlowNodeId).one();

        String handlerId = flowNode.getHandlerId();
        BusinessHandler businessHandler = BusinessHandlerRouter.getBusinessHandler(handlerId);
        if (businessHandler == null) {
            return;
        }
        businessHandler.startHandle();
    }

    /**
     * 完成当前节点
     *
     * @param instanceId 流程id
     * @param variables  审批参数
     */
    public void complete(String instanceId, Map<String, Object> variables) throws Exception {
        // 找到流程实例
        ProcessInstance processInstance = processInstanceService.lambdaQuery().eq(ProcessInstance::getInstanceId, instanceId).one();

        if (processInstance.getStatus().equals(FlowStatusEnum.COMPLETED.getStatus())) {
            throw new IllegalStateException("流程已结束");
        }


        // 执行启动方法
        String currentFlowNodeId = processInstance.getCurrentNodeId();
        // 获取开始节点
        FlowNode flowNode = flowNodeService.lambdaQuery().eq(FlowNode::getNodeId, currentFlowNodeId).one();

        String handlerId = flowNode.getHandlerId();

        // 节点类型
        String nodeType = flowNode.getNodeType();

        // 人工节点校验是否有签收人
        if (NodeTypeEnum.USER_TASK.equals(NodeTypeEnum.valueOf(nodeType))) {
            if (!StringUtils.hasText(processInstance.getAssignee())) {
                throw new IllegalStateException("当前节点未签收");
            }
        }


        // 执行完成方法
        BusinessHandler businessHandler = BusinessHandlerRouter.getBusinessHandler(handlerId);

        FlowContext flowContext = FlowContext.defaultFlowContext();

        String businessKey = processInstance.getBusinessKey();
        flowContext.setBusinessKey(businessKey);


        // 开启事务
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus transaction = transactionManager.getTransaction(definition);

        try {
            if (businessHandler != null) {
                flowContext.setVariables(variables);
                flowContext.setCurrentNodeId(currentFlowNodeId);
                flowContext.setProcessInstance(processInstance);
                businessHandler.completeHandle(flowContext);
                log.info("自定义业务流程执行完成:{}", flowContext);
            }
        } catch (Exception e) {
            log.error("工作流完成当前流程异常", e);
            transactionManager.rollback(transaction);
            return;
        }

        // 事务同步注册后续事件(提交前)
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void beforeCommit(boolean readOnly) {
                log.info("开始执行同步事务事件，执行工作流推进");
                try {
                    completeFlow(processInstance, flowNode, flowContext);
                } catch (Exception e) {
                    log.error("工作流推进完成流程异常", e);
                    throw new RuntimeException(e.getMessage());
                }
            }
        });


        transactionManager.commit(transaction);
    }


    /**
     * 构建实例
     *
     * @param flowDomain  流程领域
     * @param businessKey
     */
    private ProcessInstance buildInstance(String flowDomain, String businessKey) {

        ProcessDefinition processDefinition = processDefinitionService.lambdaQuery().eq(ProcessDefinition::getFlowDomain, flowDomain).one();
        String definitionId = processDefinition.getDefinitionId();
        ProcessInstance processInstance = new ProcessInstance();
        processInstance.setInstanceId(generateFlowId(flowDomain));
        processInstance.setDefinitionId(definitionId);
        processInstance.setBusinessKey(businessKey);
        processInstance.setCreateTime(new Date());
        processInstance.setStatus(FlowStatusEnum.RUNNING.getStatus());
        processInstance.setUpdateTime(new Date());
        processInstance.setCurrentNodeId(processDefinition.getStartNodeId());
        return processInstance;
    }

    /**
     * 生成流程id
     *
     * @param flowDomain 流程领域
     */
    private static String generateFlowId(String flowDomain) {
        String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase(Locale.ROOT).substring(0, 8);
        return flowDomain + uuid;
    }

    private void completeFlow(ProcessInstance processInstance, FlowNode flowNode, FlowContext flowContext) throws Exception {
        // 下一节点获取complete方法中的自定义下节点
        String nextNodeId = flowContext.getNextNodeId();

        String nodeType = flowNode.getNodeType();

        String currentFlowNodeId = processInstance.getCurrentNodeId();
        String instanceId = processInstance.getInstanceId();
        // 节点类型枚举，根据类型判断下一节点
        NodeTypeEnum nodeTypeEnum = NodeTypeEnum.valueOf(nodeType);

        if (NodeTypeEnum.AUTO_TASK.equals(nodeTypeEnum)) {
            // 查找优先级最高的节点
            List<NodeRouter> list = nodeRouterService.lambdaQuery().eq(NodeRouter::getNodeId, currentFlowNodeId).orderByAsc(NodeRouter::getPriority).list();
            if (CollectionUtils.isEmpty(list)) {
                throw new IllegalStateException(currentFlowNodeId + "自动通过流程未匹配到下一节点");
            }
            nextNodeId = list.get(0).getNextNodeId();
        } else {
            // 非自动流程
            if (StringUtils.hasText(nextNodeId)) {
                NodeRouter one = nodeRouterService.lambdaQuery().eq(NodeRouter::getNodeId, currentFlowNodeId).eq(NodeRouter::getNodeId, currentFlowNodeId).eq(NodeRouter::getDefinitionId, processInstance.getDefinitionId()).one();
                if (one == null) {
                    throw new IllegalStateException("节点：" + currentFlowNodeId + "到节点：" + nextNodeId + " 不是合法的路由!");
                }
            } else {

                NodeRouter nodeRouter = nodeRouterService.lambdaQuery().eq(NodeRouter::getNodeId, currentFlowNodeId).eq(NodeRouter::getConclusion, "router").one();

                if (nodeRouter != null) {
                    String expression = nodeRouter.getNextNodeId();
                    nextNodeId = String.valueOf(AviatorUtils.execute(expression, flowContext.getVariables()));
                    log.info("规则执行结论nextNodeId：{}", nextNodeId);
                } else {
                    // 获取审批结论
                    String conclusion = flowContext.getConclusion();
                    if (!StringUtils.hasText(conclusion)) {
                        throw new IllegalStateException(currentFlowNodeId + " 非自动节点，审批结论不能为空");
                    }

                    // 查找路由
                    nodeRouter = nodeRouterService.lambdaQuery().eq(NodeRouter::getNodeId, currentFlowNodeId).eq(NodeRouter::getConclusion, conclusion).one();
                    if (nodeRouter == null) {
                        throw new IllegalStateException(currentFlowNodeId + "自动通过流程未匹配到下一节点");
                    }
                    nextNodeId = nodeRouter.getNextNodeId();
                }
            }
        }


        if (nextNodeId == null) {
            throw new Exception("下一节点不能为空");
        }

        log.info("流程:{} 突进到节点:{}", processInstance.getInstanceId(), nextNodeId);

        // 更新流程实例信息,模拟更新
        processInstance.setAssignee(null);
        processInstance.setPreviousNodeId(currentFlowNodeId);
        processInstance.setCurrentNodeId(nextNodeId);

        // 结束当前history
        Integer historyId = processInstance.getHistoryId();
        if (historyId != null) {
            NodeHistory currentHistory = nodeHistoryService.getById(historyId);
            currentHistory.setEndTime(new Date());
            currentHistory.setDuration(Math.toIntExact(new Date().getTime() - currentHistory.getStartTime().getTime()));
            currentHistory.setCompleted(true);
            nodeHistoryService.updateById(currentHistory);
        }


        // 如果是结束节点
        if (nextNodeId.equals("end")) {
            processInstance.setStatus(FlowStatusEnum.COMPLETED.getStatus());
            processInstance.setHistoryId(null);
            // 模拟结束
            log.info("instanceId:{} 流程执行结束", instanceId);
        } else {
            // 初始化下一节点
            NodeHistory historyEntity = new NodeHistory();
            historyEntity.setNodeId(processInstance.getCurrentNodeId());
            historyEntity.setStartTime(new Date());
            historyEntity.setInstanceId(processInstance.getInstanceId());
            historyEntity.setCompleted(false);
            nodeHistoryService.save(historyEntity);
            processInstance.setHistoryId(historyEntity.getId());
        }

        processInstanceService.saveOrUpdate(processInstance);
    }

}
